﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace thailandProject.Models
{
    /// <summary>
    /// TO USE:
    /// Call getCompleteMatches, getPartialMatches, or getAllMatches
    /// ApartmentPartialRange will display the date range that is valid for this apartment
    ///     Otherwise, it is empty on a complete match
    /// </summary>
    public class Test_Property
    {
        //I'm assuming that the partial matches would be returned from the database via SQL
        //public static const int PARTIAL_MATCH_DAYS = 7;

        #region Instantiated Data
        /// <summary>
        /// SUCCESS -- The begin and end date do not overlap at all for the tested values
        /// FAILURE_EXTERNAL -- The begin and end date are completely inside (or equal to) the tested values
        /// FAILURE_INTERNAL -- The begin and end date are completely outside the tested values
        /// BEGIN_OVERLAP -- The begin date is overlapped, but not the end date
        /// END_OVERLAP -- The end date is overlapped, but not the beginning date
        /// </summary>
        public enum MATCH_TYPE { SUCCESS = 0, FAILURE_EXTERNAL = 1, FAILURE_INTERNAL = 2,
            BEGIN_OVERLAP = 3, END_OVERLAP = 4 }
        
        /// <summary>
        /// A private internal class that stores a begin and end date
        /// </summary>
        public class DateRange
        {
            public DateTime BeginTime { get; set; }
            public DateTime EndTime { get; set; }

            public DateRange(DateTime b, DateTime e)
            {
                BeginTime = b;
                EndTime = e;
            }
        }
        /// <summary>
        /// When testing dates against known reservations, the collision object stores the last tested DateRange
        /// </summary>
        private DateRange collision;
        
        public string ApartmentName { get; set; }
        public LinkedList<DateRange> ApartmentReservedTimes { get; set; }
        public string ApartmentPartialRange { get; set; }

        /// <summary>
        /// Tests each known reservation against two dates
        /// Will return SUCCESS if end date = begin day or vice-versa
        /// View MATCH_TYPE enum for possible collisions
        /// </summary>
        /// <param name="begin">Beginning date for availability range</param>
        /// <param name="end">End date for availability range</param>
        /// <returns>The corresponding MATCH_TYPE for the type of date range collision</returns>
        public MATCH_TYPE isAvailable(DateTime begin, DateTime end)
        {
            foreach (DateRange dr in ApartmentReservedTimes)
            {
                collision = dr;
                //When the end of a reservation overlaps with the beginning of the test begin
                if (dr.EndTime > begin && dr.BeginTime < begin)
                    return MATCH_TYPE.BEGIN_OVERLAP;
                //When the test end overlaps with the beginning of a reservation
                if (end > dr.BeginTime && end < dr.EndTime)
                    return MATCH_TYPE.END_OVERLAP;
                //When the test dates are contained (Inclusive) inside a reservation
                if (begin <= dr.BeginTime && end >= dr.EndTime)
                    return MATCH_TYPE.FAILURE_EXTERNAL;
                //When the test dates contain (exclusive) a reservation
                if (dr.BeginTime < begin && dr.EndTime > end)
                    return MATCH_TYPE.FAILURE_INTERNAL;
            }

            collision = null;
            return MATCH_TYPE.SUCCESS; //No collisions happened
        }
        #endregion

        #region Static Functions
        /// <summary>
        /// Generates a list of hardcoded apartment values with hardcoded reservations
        /// </summary>
        /// <returns>A LinkedList containing 6 apartments</returns>
        private static LinkedList<Test_Property> generatePropertyList()
        {
            #region Apartment 214
            Test_Property a214 = new Test_Property();
            a214.ApartmentName = "Apartment 214";
            LinkedList<DateRange> a214Dates = new LinkedList<DateRange>();
            a214Dates.AddFirst(new DateRange(new DateTime(2013, 3, 1), new DateTime(2013, 3, 31))); //Mar 1 to Mar 31
            a214Dates.AddFirst(new DateRange(new DateTime(2013, 5, 3), new DateTime(2013, 6, 7))); //May 3 to Jun 7
            a214Dates.AddFirst(new DateRange(new DateTime(2013, 6, 10), new DateTime(2013, 7, 20))); //Jun 10 to Jul 20
            a214.ApartmentReservedTimes = a214Dates;
            #endregion

            #region Apartment 301
            Test_Property a301 = new Test_Property();
            a301.ApartmentName = "Apartment 301";
            LinkedList<DateRange> a301Dates = new LinkedList<DateRange>();
            a301Dates.AddFirst(new DateRange(new DateTime(2013, 3, 12), new DateTime(2013, 3, 30))); //Mar 12 to Mar 30
            a301Dates.AddFirst(new DateRange(new DateTime(2013, 4, 26), new DateTime(2013, 6, 8))); //Apr 26 to Jun 8
            a301Dates.AddFirst(new DateRange(new DateTime(2013, 6, 10), new DateTime(2013, 7, 10))); //Jun 10 to Jul 10
            a301.ApartmentReservedTimes = a301Dates;
            #endregion

            #region Apartment 355
            Test_Property a355 = new Test_Property();
            a355.ApartmentName = "Apartment 355";
            LinkedList<DateRange> a355Dates = new LinkedList<DateRange>();
            a355Dates.AddFirst(new DateRange(new DateTime(2013, 2, 1), new DateTime(2013, 3, 28))); //Feb 1 to Mar 28
            a355Dates.AddFirst(new DateRange(new DateTime(2013, 4, 30), new DateTime(2013, 5, 30))); //Apr 30 to May 30
            a355Dates.AddFirst(new DateRange(new DateTime(2013, 5, 31), new DateTime(2013, 7, 1))); //May 31 to Jul 1
            a355.ApartmentReservedTimes = a355Dates;
            #endregion

            #region Apartment 595
            Test_Property a595 = new Test_Property();
            a595.ApartmentName = "Apartment 595";
            LinkedList<DateRange> a595Dates = new LinkedList<DateRange>();
            a595Dates.AddFirst(new DateRange(new DateTime(2013, 2, 13), new DateTime(2013, 3, 13))); //Feb 13 to Mar 13
            a595Dates.AddFirst(new DateRange(new DateTime(2013, 3, 13), new DateTime(2013, 4, 1))); //Mar 13 to Apr 1
            a595Dates.AddFirst(new DateRange(new DateTime(2013, 5, 1), new DateTime(2013, 6, 30))); //May 1 to Jun 30
            a595.ApartmentReservedTimes = a595Dates;
            #endregion

            #region Apartment 496
            Test_Property a496 = new Test_Property();
            a496.ApartmentName = "Apartment 496";
            LinkedList<DateRange> a496Dates = new LinkedList<DateRange>();
            a496Dates.AddFirst(new DateRange(new DateTime(2013, 2, 28), new DateTime(2013, 4, 6))); //Feb 28 to Apr 6
            a496Dates.AddFirst(new DateRange(new DateTime(2013, 4, 30), new DateTime(2013, 5, 10))); //Apr 30 to May 10
            a496Dates.AddFirst(new DateRange(new DateTime(2013, 5, 12), new DateTime(2013, 6, 7))); //May 12 to Jun 7
            a496.ApartmentReservedTimes = a496Dates;
            #endregion

            #region Apartment 695
            Test_Property a695 = new Test_Property();
            a695.ApartmentName = "Apartment 695";
            LinkedList<DateRange> a695Dates = new LinkedList<DateRange>();
            a695Dates.AddFirst(new DateRange(new DateTime(2013, 2, 28), new DateTime(2013, 3, 26))); //Feb 28 to Mar 26
            a695Dates.AddFirst(new DateRange(new DateTime(2013, 4, 24), new DateTime(2013, 5, 15))); //Apr 24 to May 15
            a695Dates.AddFirst(new DateRange(new DateTime(2013, 5, 15), new DateTime(2013, 6, 15))); //May 15 to Jun 15
            a695.ApartmentReservedTimes = a695Dates;
            #endregion

            LinkedList<Test_Property> rt = new LinkedList<Test_Property>();

            #region Add Properties
            rt.AddLast(a214);
            rt.AddLast(a301);
            rt.AddLast(a355);
            rt.AddLast(a496);
            rt.AddLast(a595);
            rt.AddLast(a695);
            #endregion

            return rt;
        }

        /// <summary>
        /// Returns complete matches based on the hardcoded apartment data
        /// </summary>
        /// <param name="begin">Beginning date of the reservation</param>
        /// <param name="end">End date of the reservation</param>
        /// <returns>List showing only complete matches</returns>
        public static IEnumerable<Test_Property> getCompleteMatches(DateTime begin, DateTime end)
        {
            LinkedList<Test_Property> prop = generatePropertyList();
            LinkedList<Test_Property> rt = new LinkedList<Test_Property>();

            foreach (Test_Property p in prop)
            {
                if (p.isAvailable(begin, end) == MATCH_TYPE.SUCCESS)
                    rt.AddLast(p);
            }

            return rt;
        }

        /// <summary>
        /// Returns partial matches based on the hardcoded apartment data
        /// Excludes and complete matches
        /// </summary>
        /// <param name="begin">Beginning date of the reservation</param>
        /// <param name="end">End date partial matches</returns>
        public static IEnumerable<Test_Property> getPartialMatches(DateTime begin, DateTime end)
        {
            LinkedList<Test_Property> prop = generatePropertyList();
            LinkedList<Test_Property> rt = new LinkedList<Test_Property>();

            foreach (Test_Property p in prop)
            {
                MATCH_TYPE type = p.isAvailable(begin, end);
                if (type == MATCH_TYPE.BEGIN_OVERLAP)
                {
                    //For a begin overlap, use the overlapping reservation's begin as the begin date
                    p.ApartmentPartialRange = p.collision.EndTime.ToShortDateString() + " - " + end.ToShortDateString();
                    rt.AddLast(p);
                }
                else if (type == MATCH_TYPE.END_OVERLAP)
                {
                    //For an end overlap, use the overlapping reservation's begin as the end date
                    p.ApartmentPartialRange = begin.ToShortDateString() + " - " + p.collision.BeginTime.ToShortDateString();
                    rt.AddLast(p);
                }
            }

            return rt;
        }

        /// <summary>
        /// Returns all matches, which include complete and partial
        /// </summary>
        /// <param name="begin">Beginning date of the reservation</param>
        /// <param name="end">End date partial matches</returns>
        public static IEnumerable<Test_Property> getAllMatches(DateTime begin, DateTime end)
        {
            LinkedList<Test_Property> prop = generatePropertyList();
            LinkedList<Test_Property> rt = new LinkedList<Test_Property>();

            foreach (Test_Property p in prop)
            {
                MATCH_TYPE type = p.isAvailable(begin, end);
                if (type == MATCH_TYPE.SUCCESS)
                {
                    rt.AddLast(p);
                }
                else if (type == MATCH_TYPE.BEGIN_OVERLAP)
                {
                    //For a begin overlap, use the overlapping reservation's begin as the begin date
                    p.ApartmentPartialRange = p.collision.EndTime.ToShortDateString() + " - " + end.ToShortDateString();
                    rt.AddLast(p);
                }
                else if (type == MATCH_TYPE.END_OVERLAP)
                {
                    //For an end overlap, use the overlapping reservation's begin as the end date
                    p.ApartmentPartialRange = begin.ToShortDateString() + " - " + p.collision.BeginTime.ToShortDateString();
                    rt.AddLast(p);
                }
            }

            return rt;
        }

        /// <summary>
        /// Returns all properties
        /// </summary>
        /// <returns>Returns all hardcoded properties</returns>
        public static IEnumerable<Test_Property> getAllProperties()
        {
            return generatePropertyList();
        }
        #endregion
    }
}